<?php
/**
 * Created by PhpStorm.
 * User: allen_ios
 * Date: 17/1/11
 * Time: 下午3:10
 */
namespace Task\Core;

class RedisQueueService
{
    /**
     * @var 类实例的静态属性
     */
    private static $_instance;

    /**
     * @var redis对象
     */
    private $redisObj;


    /**
     * 单列模式下，将构造函数设置为私有，防止外部new
     */
    private function __construct($options = array())
    {
        $this->redisObj = $this->connectRedis($options);
    }

    /**
     * 防止对象被克隆
     */
    public function __clone()
    {
        throw new \Exception('单列对象不能被克隆');
    }

    /**
     * 连接redis操作
     * @param array $options  redis的连接配置
     */
    private function connectRedis($options = array())
    {
        if (!extension_loaded('redis')) {
            throw new \Exception('_NOT_SUPPERT_:redis');
        }

        $defOption = array(
            'host' => config('REDIS_HOST') ? config('REDIS_HOST') : '127.0.0.1',
            'port' => config('REDIS_PORT') ? config('REDIS_PORT') : 6379,
            'timeout' => config('REDIS_CACHE_TIMEOUT') ? config('REDIS_CACHE_TIMEOUT') : false,
            'persistent' => false,
            'expire' => config('REDIS_CACHE_TIMEOUT'),
            'passwd' => config('REDIS_PASS'),
            'select_db' => config('REDIS_DEF_DB'),
        );
        if (!empty($options)) {
            $options = array_merge($defOption,$options);
        } else {
            $options = $defOption;
        }

        $func = $options['persistent'] ? 'pconnect' : 'connect';
        $redis = new \Redis();
        $connected = $options['timeout'] === false
            ? $redis->$func($options['host'], $options['port'])
            : $redis->$func($options['host'], $options['port'], $options['timeout']);

        if ( !$redis->auth($options['passwd']) ) {
            throw new \Exception('_REDIS_AUTH_ERROR_');
        }

        $redis->select($options['select_db']);
        return $redis;
    }

    /**
     * Singleton instance（获取自己的实例）
     * @param array $options
     * @return RedisQueueService|类实例的静态属性
     */
    public static function getInstance($options = array())
    {
        if (null === self::$_instance) {
            self::$_instance = new self($options);
        }

        return self::$_instance;
    }

    /**
     * 入队列，队列的内容是以json串存储，默认是采用头部进，尾部取
     * @param string $listKey   队列名称
     * @param mixed $listData   队列内容，可以是一个数组，可以是一个字符串
     * @param string $pushType  入队列方式head|tail
     */
    public function push($listKey, $listData, $pushType='head')
    {
        $listKey = $this->_key($listKey, 'list');   //格式化队列key
        if (is_array($listData)) {                  //如果队列数据是数组
            $listData = json_encode($listData);
        }

        $res = 0;   //执行结果

        if (!$pushType) {
            $pushType = 'head';
        }

        if ($pushType == 'head') {  //往队列头塞元素
            $res = $this->redisObj->lpush($listKey,$listData);
        } elseif ($pushType == 'tail') {    //往队列尾赛元素
            $res = $this->redisObj->rPush($listKey,$listData);
        }
        return $res;
    }

    /**
     * 出队列,默认是采用头部进，尾部取
     * @param string $listKey 队列名称
     * @param string $pullType 出队列的方式
     * @param bool $isBlock    是否堵塞模式
     * @param int $blockTime   堵塞时间
     */
    public function pull($listKey, $pullType='tail', $isBlock=false, $blockTime=1)
    {
        $listKey = $this->_key($listKey,'list');
        if ( !$this->redisObj->exists($listKey) ) { //判断队列的key是否存在
            return false;
        }

        if (!$pullType) {
            $pullType == 'tail';
        }

        $data = '';
        if ($pullType == 'head')
        {
            //是否采用堵塞式的去取数据
            if ($isBlock == false) {
                $data = $this->redisObj->lpop($listKey);
            } else {
                //返回队列名称和队列的内容的数组
                $tmpData = $this->redisObj->blpop($listKey,$blockTime);
                $data = $tmpData[1];
            }
        }
        elseif ($pullType == 'tail')
        {
            //是否采用堵塞式的去取数据
            if ($isBlock == false) {
                $data = $this->redisObj->rpop($listKey);
            } else {
                $tmpData = $this->redisObj->brpop($listKey,$blockTime);
                $data = $tmpData[1];
            }
        }

        if (is_array($data)) {
            $listData = json_decode($data,true);
        } else {
            $listData = $data;
        }

        return $listData;
    }

    /**
     * 将一个队列移到另一个队列
     * @param string $srcListName   队列数据源
     * @param string $desListName   目标队列
     */
    public function removeList($srcListName, $desListName)
    {
        $result = $this->redisObj->rpoplpush($srcListName,$desListName);
        return $result;
    }

    /**
     * 获得队列里面的列表数据,多条的
     * @param stirng $listKey 队列名称
     * @param int $start 开始位置 0表示取第一条
     * @param int $end 结束位置 -1表示取全部
     * @return array()
     */
    public function getList($listKey,$start=0,$end)
    {
        $listKey = $this->_key($listKey,'list');
        if ( !$this->redisObj->exists($listKey) ) { //判断队列的key是否存在
            return false;
        }

        $data = $this->redisObj->lRange($listKey,$start,$end);
        return $data;
    }

    /**
     * 获得队列的单条数据，不删除
     * @param string $listKey 队列名称
     * @param int $index 元素的位置
     * @return string
     */
    public function getOne($listKey, $index)
    {
        $listKey = $this->_key($listKey,'list');
        if ( !$this->redisObj->exists($listKey) ) {     //判断队列的key是否存在
            return false;
        }

        return $this->redisObj->lIndex($listKey,$index);
    }

    /**
     * 获得队列的长度
     * @param string $listKey   队列的键值
     * @return int
     */
    public function getListLenth($listKey)
    {
        $listKey = $this->_key($listKey,'list');
        if ( !$this->redisObj->exists($listKey) ) {
            return 0;
        }

        return $this->redisObj->lLen($listKey);
    }

    /**
     * 键值类型
     * @param string $keyName   键名称
     * @param string $keyType   键类型
     * @return string
     */
    private function _key($keyName, $keyType)
    {
        if ($keyType) {
            $keyType = $keyType.'_';
        }
        return $keyType.$keyName;
    }

    /**
     * 关闭redis连接
     */
    private function closeRedis()
    {
        $this->redisObj->close();
    }

    /**
     * 单列模式下，析构函数设置为私有，防止外部来释放
     */
    public function __destruct()
    {
        $this->closeRedis();
    }
}